class ToolLetterEditListener implements ControlListener 
{
  Tool parent;
  ToolLetterEditListener(Tool parent)
  {
    this.parent = parent;
  }

  public void controlEvent(ControlEvent theEvent) 
  {
    String name = theEvent.controller().name();
    if (name.length()==1) {
      this.parent.onLetterChosen(name.charAt(0));
      this.parent.toggleEdited = (GMLToggle)theEvent.controller();
    }
    else
    {
      if (name.equals("validate")) {
        //this.parent.validateLetterEdited();
      }
      else if (name.equals("clear")) {
        this.parent.clearLetterEdited();
      }
      else if (name.equals("replay")) {
        this.parent.replayLetterEdited();
      }
    }
  }
}

public class Tool implements ControlListener 
{
  PApplet theApplet;

  // Controls
  ControlP5 controls;
  DropdownList ddFonts;
  Textfield tfArtist;
  Textfield tfStyleName;

  ToolLetterEditListener leListener;
  RadioButton rb;
  ColorPicker cpStrokeColor;
  ControlGroup groupLetterEdited;
  ControlGroup groupControls;
  ControlGroup groupControlsTest;
  ControlGroup groupStyles;
  ControlGroup groupGrid;
  GMLToggle toggleEdited;
  Slider sliderStrokeWeight;
  Slider sliderGridNbCases;
  Slider sliderGridExtent;
  Slider sliderGridBaseline;
  Slider sliderGridAlpha;
  int paddingToggle=1;

  // Fonts
  FontLoader fontLoader;
  List<String> fontList;
  int selectedFontIndex = 0;
  FontWriter fontWriter;

  // GML
  GmlRecorder gmlRecorder;
  GmlBrushManager gmlBrushManager;
  GmlLinesDrawer gmlLinesDrawer;
  float gmlScale;
  boolean gmlIsRecording=false;
  boolean gmlIsReplaying=false;
  AABB letterEditedAABB;
  
  // Grid
  ToolGrid grid;

  // Test
  boolean isTest = false;
  boolean isGroupLetterEditedVisible;
  
  // Replay
  gml4u.utils.Timer timerReplay;
  float durationReplay = 2.0;

  // Data
  Font font;
  Letter letterEdited;
  PImage bgImage;

  // Text
  String stringTyped = "";


  // --------------------------------------
  float getGridWidth()
  {
    return grid.getWidth();
  }

  // --------------------------------------
  Tool(PApplet applet)
  {
    theApplet = applet;
    leListener = new ToolLetterEditListener(this);

    gmlRecorder = new GmlRecorder(new Vec3D(1,1,1));
    gmlBrushManager = new GmlBrushManager();
    gmlLinesDrawer = new GmlLinesDrawer();
    gmlBrushManager.setDefault( gmlLinesDrawer );

    fontLoader = new FontLoader(this, sketchPath("data")+"/");
    println("Fonts dolder "+sketchPath("data"));

    fontWriter = new FontWriter();

    bgImage = theApplet.loadImage("GRLFR_Logo.png");
    grid = new ToolGrid(10);
    grid.setExtent(0.4*float(theApplet.width));
    timerReplay = new gml4u.utils.Timer();
  }

  // --------------------------------------
  public void createControls()
  {
    controls = new ControlP5(theApplet);

    int x = 2;
    int y = 2;
    int width = 140;
    int height = 18;

    groupControls = controls.addGroup("controls", 0, 0);
    groupControls.hideBar();

    rb = controls.addRadioButton("letters", 0, 0);
    rb.setGroup(groupControls);

    ddFonts = controls.addDropdownList("ddFonts", x, y+height, width*2, height*5);
    ddFonts.setLabel("Font list");

    ddFonts.setBackgroundColor(color(190));
    ddFonts.setItemHeight(20);
    ddFonts.setBarHeight(15);
    ddFonts.captionLabel().set("Font list");
    ddFonts.captionLabel().style().marginTop = 3;
    ddFonts.captionLabel().style().marginLeft = 3;
    ddFonts.valueLabel().style().marginTop = 3;
    ddFonts.setGroup(groupControls);
    updateFontList();

    x+=width*2+2;

    Button btnLoad = controls.addButton("load", 2, x, y, 30, height);
    btnLoad.addListener(this);
    btnLoad.setGroup(groupControls);

    x+=width+2;


    tfArtist = controls.addTextfield("tfArtist", x, y, width, height);
    tfArtist.setAutoClear(false);
    tfArtist.setLabel("Artist");
    tfArtist.addListener(this);
    tfArtist.setGroup(groupControls);

    x+=width+2;

    tfStyleName = controls.addTextfield("tfStyleName", x, y, width, height);
    tfStyleName.setAutoClear(false);
    tfStyleName.setLabel("Style name");
    tfStyleName.addListener(this);
    tfStyleName.setGroup(groupControls);

    x+=width+2;

    Button btnSave = controls.addButton("save", 1, x, y, 30, height);
    btnSave.addListener(this);
    btnSave.setGroup(groupControls);
    x+=32;

    Button btnReset = controls.addButton("reset", 2, x, y, 30, height);
    btnReset.addListener(this);
    btnReset.setGroup(groupControls);

    x+=32;

    Button btnTest = controls.addButton("test", 3, x, y, 30, height);
    btnTest.addListener(this);
    btnTest.setGroup(groupControls);

    // ALPHABET

    y+=height+26;

    rb.setPosition(2, y);
    rb.setItemsPerRow(26);
    rb.setSpacingColumn(0);

    // STYLE PARAMETERS

   // y = (int)rb.position().y + rb.getHeight()+2;
    y += 76; // Temporary

    int strokeWeightMin = 1;
    int strokeWeightMax = 7;
    groupStyles = controls.addGroup("style", 2, y, width);

    sliderStrokeWeight = controls.addSlider("stroke weight", strokeWeightMin, strokeWeightMax, 1, 0, 2, width, height);
//    sliderStrokeWeight.alignValueLabel(ControlP5.BOTTOM);
    sliderStrokeWeight.setLabelVisible(false);
    sliderStrokeWeight.setGroup(groupStyles);
    //sliderStrokeWeight.setNumberOfTickMarks(strokeWeightMax-strokeWeightMin+1);
    //sliderStrokeWeight.showTickMarks(false);
    sliderStrokeWeight.addListener(this);

    cpStrokeColor = controls.addColorPicker("stroke color", 0, 22, width, height);  
    cpStrokeColor.hideBar();
    cpStrokeColor.setGroup(groupStyles);

    // GRID CONTROLS
    groupGrid = controls.addGroup("grid", 2, y, width);
    sliderGridNbCases = controls.addSlider("nb", 10, 50, 25, 0, 2, width, height);
//    sliderGridNbCases.setLabelVisible(false);
    sliderGridNbCases.setGroup(groupGrid);
    sliderGridNbCases.addListener(this);

    sliderGridExtent = controls.addSlider("extent", 0.4*float(theApplet.width), 0.65*float(theApplet.width), 0.4*float(theApplet.width), 0, 22, width, height);
//    sliderGridExtent.setLabelVisible(false);
    sliderGridExtent.setGroup(groupGrid);
    sliderGridExtent.addListener(this);

    sliderGridBaseline = controls.addSlider("baseline", 0, 1, 0.65, 0, 42, width, height);
//    sliderGridBaseline.setLabelVisible(false);
    sliderGridBaseline.setGroup(groupGrid);
    sliderGridBaseline.addListener(this);
    
    sliderGridAlpha = controls.addSlider("alpha", 0, 255, 100, 0, 62, width, height);
//    sliderGridBaseline.setLabelVisible(false);
    sliderGridAlpha.setGroup(groupGrid);
    sliderGridAlpha.addListener(this);
    
    
    groupGrid.setPosition(2, y+110);


    // LETTER CONTROLS

    groupControlsTest = controls.addGroup("controlsTest", 0, 0);
    groupControlsTest.hideBar();
    groupControlsTest.hide();

    Button btnTestBack = controls.addButton("back to edit", 2, 2, 2, 65, height);
    btnTestBack.addListener(this);
    btnTestBack.setGroup(groupControlsTest);

    groupLetterEdited = controls.addGroup("groupLetterEdited", 100, 100);
    groupLetterEdited.hideBar();

    /* useless ?
     Button btnValidate = controls.addButton("validate", 0, 0, 0, 50, 20);
     btnValidate.setGroup(groupLetterEdited);
     btnValidate.addListener(leListener);
     */

    Button btnClear = controls.addButton("clear", 0, 0/*50+2*/, 0, 50, 20);
    btnClear.setGroup(groupLetterEdited);
    btnClear.addListener(leListener);

     Button btnReplay = controls.addButton("replay", 0, 50+4, 0, 50, 20);
     btnReplay.setGroup(groupLetterEdited);
     btnReplay.addListener(leListener);
     

    groupLetterEdited.hide();
  }



  // --------------------------------------
  void updateFontList() {
    println("updating font list");
    fontList = fontLoader.getAvailableFonts();
    println("Font list size "+fontList.size());
    ddFonts.clear();
    for (int i=0; i<fontList.size(); i++) {
      println("Font : "+fontList.get(i).replaceAll(".+[/\\\\](.+)", "$1"));
      ddFonts.addItem(fontList.get(i).replaceAll(".+[/\\\\](.+)", "$1"), i);
    }
  }

  // --------------------------------------
  // gml event
  // Callback method
  public void gmlEvent(GmlEvent event) {
    if (event instanceof GmlMultiSavingEvent) {
      Map<String, Boolean> results = new HashMap<String, Boolean>();
      results.putAll(((GmlMultiSavingEvent) event).locations);
      println(results.size() + " files saved");
      for (String location : results.keySet()) {
        if (!results.get(location)) {
          println(location +" saving failed");
        }
        else {
          println(location +" saving OK");
        }
      }
      updateFontList();
    }
    else if (event instanceof GmlMultiParsingEvent) {
      // If so, get the Gml
      Map<String, Gml> gmlList = new HashMap<String, Gml>();
      gmlList = (((GmlMultiParsingEvent) event).gmlList);

      if (gmlList.size() > 0) {
        String fontFolder = gmlList.keySet().iterator().next();
        // Get artist, font name from the first location in the list
        String folderName = fontFolder.replaceAll(".+[/\\\\](.+)[/\\\\].+\\.gml", "$1");
        String artistName = folderName.replaceAll("(.+)-.+", "$1");
        String fontName = folderName.replaceAll("[^-]+-(.+)", "$1");

        println("Font "+artistName+ " / "+fontName);

        // Create font
        Font newFont = new Font(artistName, fontName);

        // Set letters
        for (String letter : gmlList.keySet()) {
          String ch = letter.replaceAll(".+[/\\\\]([^/\\\\]+)\\.gml", "$1");
          char lettr = ch.toLowerCase().charAt(0);
          Letter l = new Letter(lettr);
          l.gml = gmlList.get(letter);
          println("adding new letter "+lettr);
          newFont.letters.put(ch, l);
        }
        println("done");
        font = newFont;
        updateControls();
      }
    }
  }


  // --------------------------------------
  public void controlEvent(ControlEvent theEvent) 
  {
    String name = theEvent.controller().name();
    if (name.equals("tfArtist")) {
      // TODO test special characters
      font.artist = theEvent.stringValue();
    }
    else if (name.equals("tfStyleName")) {
      // TODO test special characters
      font.name = theEvent.stringValue();
    }
    else if (name.equals("load")) {

      // TODO 
      int index = (int) controls.getGroup("ddFonts").value();
      if (null != fontList.get(index)) {
        fontLoader.loadFont(fontList.get(index));
      }
    }
    else if (name.equals("save")) {
      tfArtist.submit();
      tfStyleName.submit();
      fontLoader.saveFont(font);
    }
    else if (name.equals("reset")) {
      font.resetLetters();
      updateControls();
    }
    else if (name.equals("test")) {
      isTest = true;
      groupControls.hide();
      groupControlsTest.show();
      groupStyles.hide();
      groupGrid.hide();
      isGroupLetterEditedVisible = groupLetterEdited.isVisible();
      groupLetterEdited.hide();
    }
    else if (name.equals("back to edit")) {
      isTest = false;
      groupControls.show();
      groupControlsTest.hide();
      groupStyles.show();
      groupGrid.show();
      groupLetterEdited.setVisible(isGroupLetterEditedVisible);
      stringTyped="";
      fontWriter.setString("");
    }
    else if (name.equals("stroke weight"))
    {
      gmlLinesDrawer.setStrokeWeight(int(theEvent.controller().value()));
    }
    else if (name.equals("nb"))
    {
      grid.setNumber(int(theEvent.controller().value()));
    }
    else if (name.equals("extent"))
    {
      grid.setExtent(theEvent.controller().value());
      updateControlLetterEditedAABB();
    }
    else if (name.equals("baseline"))
    {
      grid.setBaseLine(theEvent.controller().value());
    }
    else if (name.equals("alpha"))
    {
      grid.setAlpha(theEvent.controller().value());
    }

  }

  // --------------------------------------
  public void onLetterChosen(char c)
  {
    letterEdited = font.getLetter(c);
    if (letterEdited != null && letterEdited.gml!=null) 
    {
      // Bounding
      letterEditedAABB = letterEdited.gml.getBoundingBox();
    }
    else {
      letterEditedAABB = null;
    }

    gmlRecorder.clear();
    timerReplay.reset();

    // Controls
    updateControlLetterEditedAABB();
  }

  // --------------------------------------
  public void clearLetterEdited()
  {
    gmlRecorder.clear();
    letterEdited.gml = null;
    letterEditedAABB = null;

    // Controls
    updateControlLetterEditedAABB();
  }

  // --------------------------------------
  public void replayLetterEdited()
  {
    println("replayLetterEdited");
    timerReplay.start();
    if (letterEdited.gml != null){
      GmlUtils.timeBox(letterEdited.gml, durationReplay, true);
      println("  - timebox starting");
    }
  }

  // --------------------------------------
  public void validateLetterEdited()
  {
    gmlRecorder.clear();
    letterEditedAABB = null;

    // Controls
    updateControlLetterEditedAABB();
    rb.deactivateAll();
  }

  // --------------------------------------
  void loadFont()
  {
    // TEMP
    if (null == font) {
      font = new Font("unknown", "untitled");
    }

    // Reset
    letterEdited = null;
    toggleEdited = null;

    // Update controls
    createToggles();
    updateControls();
  }

  // --------------------------------------
  void createToggles()
  {
    for (int i=0;i<lettersDef.length;i++)
    {
      char c = lettersDef[i];

      // Remove toggle
      rb.removeItem(""+c);

      // Add new toggle
      int nbLetters = lettersDef.length;
      int sizeToggle = int((float)width / (float)nbLetters);
      GMLToggle t = new GMLToggle(controls, (Tab) controls.controlWindow.tabs().get(1), ""+c, 0, 0, 0, sizeToggle, sizeToggle);
      t.setLetter( font.getLetter(c) );
      t.addListener(leListener);
      rb.setHeight(sizeToggle);
      rb.addItem(t, 0);
    }
  }

  // --------------------------------------
  void updateControlLetterEdited()
  {
    toggleEdited.redraw();
  }

  // --------------------------------------
  void updateControlLetterEditedAABB()
  {
    if (letterEditedAABB == null)
    {
      groupLetterEdited.hide();
    }
    else
    {
      float w = letterEditedAABB.getExtent().x*grid.getWidth()*2;
      float h = letterEditedAABB.getExtent().y*grid.getWidth()*2;
      float x = grid.x()+letterEditedAABB.x*grid.getWidth()-w/2; 
      float y = grid.y()+letterEditedAABB.y*grid.getWidth()+h/2; 
      groupLetterEdited.setPosition(x, y+4);
      groupLetterEdited.show();
    }
  }

  // --------------------------------------
  void updateControls()
  {
    // Artist name
    tfArtist.setValue(font.artist);

    // Style name
    tfStyleName.setValue(font.name);

    for (int i=0;i< lettersDef.length;i++)
    {

      GMLToggle t = (GMLToggle) rb.getItem(i);
      t.setLetter(font.getLetter(lettersDef[i]));
      t.redraw();
    }
    
    // Reposition dynamically
    groupStyles.setPosition(groupStyles.position().x, rb.getHeight()+rb.position().y+20);
    groupGrid.setPosition(groupStyles.position().x+groupStyles.getWidth()+4,groupStyles.position().y);
  }  
 
  // --------------------------------------
  void drawGmlStrokes(Collection<GmlStroke> strokes)
  {
        if (strokes !=null) 
        {
          gmlLinesDrawer.setStrokeColor(cpStrokeColor.getColorValue());
          pushMatrix();
          translate(grid.x(),grid.y(),0);
          for (GmlStroke stroke : strokes)
            gmlLinesDrawer.draw(g, stroke, grid.getWidth());
          popMatrix();
    }
  }
  // --------------------------------------
  public void draw()
  {
    // Image
    theApplet.pushStyle();
    theApplet.tint(255, 40);
    theApplet.image(bgImage, (width-bgImage.width)/2, (height-bgImage.height)/2);
    theApplet.popStyle();


    // Edit
    if (isTest == false)
    {
      // Draw grid
      grid.draw();    

      if (letterEdited!=null)
      {
        Collection<GmlStroke> strokes=null;

        // Draw the strokes being recorded
        if (gmlIsRecording)
        {
          drawGmlStrokes( gmlRecorder.getStrokes() );
        }
        // Draw the strokes being saved
        else
        {
          if (letterEdited.gml != null) 
          {
              if (timerReplay.started() == true)
              {
                // Good way ? 
                //timer.tick();
                //if (timer.getTime()>=durationReplay)
                  //timer.stop();
                
                pushMatrix();
                translate(grid.x(),grid.y(),0);
                gmlBrushManager.draw(theApplet.g,letterEdited.gml,grid.getWidth());
                popMatrix();
              }
              else
                drawGmlStrokes( letterEdited.gml.getStrokes() );
          }
        }

        // AABB
        if (letterEditedAABB!=null)
        {
          noFill();
          stroke(0xff08a2cf);
          strokeWeight(1);
          rectMode(CENTER);
          pushMatrix();
          translate(grid.x(),grid.y(),0);
          rect(letterEditedAABB.x*grid.getWidth(), letterEditedAABB.y*grid.getWidth(), letterEditedAABB.getExtent().x*grid.getWidth()*2, letterEditedAABB.getExtent().y*grid.getWidth()*2);
          popMatrix();
        }
      }
    }
    // Test
    else
    {
      float margin = 50.0;
      fontWriter.draw(font,margin,margin,width-2*margin);
    }
  }

  // --------------------------------------
  public void drawControls()
  {
    controls.draw();
  }

  // --------------------------------------
  boolean isMouseOverControls()
  {
    return controls.window(theApplet).isMouseOver();
  }

  // --------------------------------------
  public void mousePressed()
  {
    if (isTest == false)
    {
      // Letter control was selected before
      if (letterEdited!=null)
      {
        // Canvas area
        if (!isMouseOverControls())
        {
          // Data
          gmlIsRecording = true;
          GmlBrush brush = new GmlBrush();
          brush.set(GmlBrush.UNIQUE_STYLE_ID, GmlLinesDrawer.ID);
          gmlRecorder.beginStroke(0, 0, brush);
          timerReplay.reset();

          // Controls
          letterEditedAABB = null;
          updateControlLetterEditedAABB();
        }
      }
    }
  }

  // --------------------------------------
  public void mouseDragged()
  {
    if (isTest == false)
    {
      if (letterEdited != null)
      {
        if (!isMouseOverControls())
        {
//          Vec3D v = new Vec3D((float) mouseX/width, (float) mouseY/height, 0);
          float mx = (float)mouseX-grid.x();
          float my = (float)mouseY-grid.y();
          Vec3D v = new Vec3D(mx/grid.getWidth(), my/grid.getHeight(), 0);
          gmlRecorder.addPoint(0, v, 0.1);
        }
      }
    }
  }

  // --------------------------------------
  public void mouseReleased() {
    if (isTest == false) 
    {
      if (gmlIsRecording)
      {
        // Stop recording
        gmlIsRecording = false;
        gmlRecorder.endStroke(0);
        letterEdited.gml = gmlRecorder.getGml().copy();
        letterEditedAABB = letterEdited.gml.getBoundingBox();
        
        // Controls
        updateControlLetterEdited();
        updateControlLetterEditedAABB();
      }
    }
  }

  // --------------------------------------
  public void keyPressed() {
    if (isTest == true)
    {
      //       if (!isMouseOverControls())
      {
        int l = stringTyped.length();
        if (key == BACKSPACE)
        {
          if (l > 0)       
            stringTyped = stringTyped.substring(0, l-1);
        }
        else
          stringTyped += key;
        fontWriter.setString(stringTyped);
      }
    }
  }
};

